Skip to content

Conversation

TheSniperFan
Copy link

Currently SDL_gpu_vulkan.c hardcodes API version 1.0 when creating the Vulkan instance for the program. This creates a problem for my use-case.

I'd like to use Slang for shader development, but SPIR-V 1.0 support is experimental. I tried two solutions and both work:

  1. Slang's experimental compiler flags which make Slang transpile to GLSL first and then to SPIR-V, rather than compiling directly to SPIR-V.
  2. Add a hint to SDL to allow creating instances with higher API versions.

I looked at the Vulkan documentation (specifically this and this). It says "Deprecated items will still work in current Vulkan implementations", which makes me prefer option 2. A Vulkan application requesting 1.3, but using only 1.0 features should work just fine. And using a hint doesn't change SDL's default behavior.

Since I don't know why this was hardcoded, I figured I'd first ask if there's any chance of this merge request going anywhere. Before I waste time cleaning something up that's not going to be merged for reasons I didn't know of.

This adds SDL_HINT_VULKAN_REQUEST_API_VERSION to allow requesting a
specific API version when SDL creates the Vulkan instance.
The patch version shouldn't be relevant to the API and a new major
version will likely require an entirely new renderer anyway.
The benefit of this approach is that it massively simplifies parsing of
the hint.
@thatcosmonaut
Copy link
Collaborator

We hardcode to 1.0 to provide maximum driver compatibility. I'm totally fine with allowing an opt-in to higher versions.

@slouken slouken added this to the 3.4.0 milestone Oct 10, 2025
Comment on lines 4086 to 4087
* This hint should be set before creating a Vulkan window. Expects a positive
* integer. E.g. 3 for Vulkan 1.3.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's reasonable to allow specifying the full version and will be more intuitive for users. This is easily implemented with, e.g.

if (SDL_sscanf(hint, "%d.%d", &major, &minor) == 2) {
    ...
} else {
    // Parse error...
}

@TheSniperFan
Copy link
Author

This needs a bit more time in the oven, I think.
Setting the API version during instance creation does allow Slang-compiled shaders to be loaded without the validation layer complaining. However, Slang might emit SPIR-V that relies on physical device features that would have to be specified during device creation.
I'll see if there's a sane, non-intrusive way to implement that tomorrow.

Introduces a new property to use when creating a Vulkan renderer.
SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER
Since the developer can now pass additional features to be activated,
this name makes more obvious that these get enabled by SDL regardless.
@TheSniperFan
Copy link
Author

TheSniperFan commented Oct 11, 2025

I added a workflow to opt-into Vulkan device features using properties. I made sure the default behavior is unaffected. The usage code in my application look like this:

props := sdl.CreateProperties()
sdl.SetBooleanProperty(props, sdl.PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, true)
sdl.SetBooleanProperty(props, sdl.PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, true)
sdl.SetStringProperty(props, sdl.PROP_GPU_DEVICE_CREATE_NAME_STRING, "vulkan")

// Enable Vulkan 1.1 feature
vulkan_11_features := vk.PhysicalDeviceVulkan11Features {
	sType                = vk.StructureType.PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
	shaderDrawParameters = true,
}
sdl.SetPointerProperty(props, sdl.PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER, rawptr(&vulkan_11_features))

result.gpu = sdl.CreateGPUDeviceWithProperties(props)

EDIT: This still needs to handle errors from unsupported features.

KHR_portability_subset structure was being overwritten by opt-in
features.
@slouken
Copy link
Collaborator

slouken commented Oct 11, 2025

I added a workflow to opt-into Vulkan device features using properties. I made sure the default behavior is unaffected. The usage code in my application look like this:

props := sdl.CreateProperties()
sdl.SetBooleanProperty(props, sdl.PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, true)
sdl.SetBooleanProperty(props, sdl.PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, true)
sdl.SetStringProperty(props, sdl.PROP_GPU_DEVICE_CREATE_NAME_STRING, "vulkan")

// Enable Vulkan 1.1 feature
vulkan_11_features := vk.PhysicalDeviceVulkan11Features {
	sType                = vk.StructureType.PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
	shaderDrawParameters = true,
}
sdl.SetPointerProperty(props, sdl.PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER, rawptr(&vulkan_11_features))

result.gpu = sdl.CreateGPUDeviceWithProperties(props)

EDIT: This still needs to handle errors from unsupported features.

This seems like a reasonable workflow to me. @thatcosmonaut, thoughts?

@thatcosmonaut
Copy link
Collaborator

thatcosmonaut commented Oct 11, 2025

Yeah that does seem reasonable. Is this stable if we have to add fields to the struct?

@slouken
Copy link
Collaborator

slouken commented Oct 11, 2025

If increasing the Vulkan version often means chaining additional device creation structures, maybe the Vulkan version should also be a property instead of a hint?

@TheSniperFan
Copy link
Author

Yeah that does seem reasonable. Is this stable if we have to add fields to the struct?

I'm not sure I understand.

If increasing the Vulkan version often means chaining additional device creation structures, maybe the Vulkan version should also be a property instead of a hint?

I was thinking about the same thing. Right now, I'm looking into error handling. As it stands, the vkCreateDevice() call crashes when unsupported features are requested.

@thatcosmonaut
Copy link
Collaborator

Yeah that does seem reasonable. Is this stable if we have to add fields to the struct?

I'm not sure I understand.

We guarantee ABI stability in SDL, so if we add something we can't make future code changes that will cause an executable to break if a client updates the SDL3 DLL for example.

@slouken
Copy link
Collaborator

slouken commented Oct 11, 2025

Yeah that does seem reasonable. Is this stable if we have to add fields to the struct?

I'm not sure I understand.

We guarantee ABI stability in SDL, so if we add something we can't make future code changes that will cause an executable to break if a client updates the SDL3 DLL for example.

Yes, this change doesn't affect any public structures, so this is ABI safe.

@thatcosmonaut
Copy link
Collaborator

Oh I just realized it's literally a pointer to the Vulkan struct, that makes sense.

@TheSniperFan
Copy link
Author

TheSniperFan commented Oct 11, 2025

If increasing the Vulkan version often means chaining additional device creation structures, maybe the Vulkan version should also be a property instead of a hint?

My current solution looks like this:

// SDL_gpu.h

#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER    "SDL.gpu.device.create.vulkan.options"

/**
 * A structure specifying additional options when using Vulkan.
 *
 * When no such structure is provided, SDL will use Vulkan API version 1.0 and a minimal set of features.
 *
 * \since This struct is available since SDL 3.4.0.
 *
 */
typedef struct SDL_GPUVulkanOptions
{
    Uint32 vulkan_api_version; /**< The Vulkan API version to request for the instance.  */
    void *physical_device_features_1_0; /**< Pointer to a VkPhysicalDeviceFeatures struct. */
    void *physical_device_features_1_1; /**< Pointer to a VkPhysicalDeviceVulkan11Features struct. */
    void *physical_device_features_1_2; /**< Pointer to a VkPhysicalDeviceVulkan12Features struct. */
    void *physical_device_features_1_3; /**< Pointer to a VkPhysicalDeviceVulkan13Features struct. */
} SDL_GPUVulkanOptions;

Providing the various structs separately rather than in a chain makes validation and error-reporting much easier.

Does this look like a workable solution?

@slouken
Copy link
Collaborator

slouken commented Oct 12, 2025

I would prefer your original implementation which just added a single chained structure to the options. It's more flexible, and once you're using properties, you're generally speaking outside the normal development path and responsible for doing so safely.

@TheSniperFan
Copy link
Author

I would prefer your original implementation which just added a single chained structure to the options. It's more flexible, and once you're using properties, you're generally speaking outside the normal development path and responsible for doing so safely.

Yeah, I started reworking it almost immediately after I posted that yesterday. It occurred to me that this structure wasn't very forward compatible.

Also replaces the previously created hint
@TheSniperFan
Copy link
Author

I just pushed the first version of the new workflow.

// SDL_gpu.h

#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER    "SDL.gpu.device.create.vulkan.options"

/**
 * A structure specifying additional options when using Vulkan.
 *
 * When no such structure is provided, SDL will use Vulkan API version 1.0 and a minimal set of features.
 * The feature list is only allowed to contain the following structures:
 *  - VkPhysicalDeviceFeatures2
 *  - VkPhysicalDeviceVulkan11Features
 *  - VkPhysicalDeviceVulkan12Features
 *  - VkPhysicalDeviceVulkan13Features
 * 
 * \since This struct is available since SDL 3.4.0.
 *
 */
typedef struct SDL_GPUVulkanOptions
{
    Uint32 vulkan_api_version; /**< The Vulkan API version to request for the instance. Use Vulkan's VK_MAKE_VERSION or VK_MAKE_API_VERSION. */
    void *feature_list; /**< Pointer to the first element of a list of Vulkan physical device feature structs. */
} SDL_GPUVulkanOptions;

The usage code now looks something like this:

props := sdl.CreateProperties()
sdl.SetBooleanProperty(props, sdl.PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, true)
sdl.SetBooleanProperty(props, sdl.PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, true)
sdl.SetStringProperty(props, sdl.PROP_GPU_DEVICE_CREATE_NAME_STRING, "vulkan")

// Enable additional Vulkan features if needed
vulkan_10_features := vk.PhysicalDeviceFeatures2 {
	sType = vk.StructureType.PHYSICAL_DEVICE_FEATURES_2,
	features = {
		wideLines = true,
	},
}
vulkan_11_features := vk.PhysicalDeviceVulkan11Features {
	sType                = vk.StructureType.PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
	shaderDrawParameters = true,
}
vulkan_12_features := vk.PhysicalDeviceVulkan12Features {
	sType = vk.StructureType.PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
	scalarBlockLayout = true,
}
vulkan_13_features := vk.PhysicalDeviceVulkan13Features {
	sType = vk.StructureType.PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,
	inlineUniformBlock = true,
}

// Create list
vulkan_10_features.pNext = rawptr(&vulkan_11_features)
vulkan_11_features.pNext = rawptr(&vulkan_12_features)
vulkan_12_features.pNext = rawptr(&vulkan_13_features)

// Set SDL property
vulkan_options := sdl.GPUVulkanOptions {
	vulkan_api_version = vk.API_VERSION_1_3,
	feature_list       = rawptr(&vulkan_10_features),
}
sdl.SetPointerProperty(props, sdl.PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER, rawptr(&vulkan_options))

result.gpu = sdl.CreateGPUDeviceWithProperties(props)

The requested features are now error-checked early, too. It prints out the individual unsupported features which were requested:

[VERBOSE] [GPU]: SDL GPU Vulkan: Application requested unsupported physical device feature 'textureCompressionASTC_LDR'
[VERBOSE] [GPU]: SDL GPU Vulkan: Application requested unsupported physical device feature 'textureCompressionASTC_HDR'
[WARN] [GPU]: Vulkan: Failed to determine a suitable physical device

@TheSniperFan
Copy link
Author

I should add.
The list you pass is allowed to contain multiple structures of the same type. SDL combines them.
What isn't allowed is passing arbitrary structures in there. There's a whitelist:

  • VkPhysicalDeviceFeatures2
  • VkPhysicalDeviceVulkan11Features
  • VkPhysicalDeviceVulkan12Features
  • VkPhysicalDeviceVulkan13Features

Everything else causes SDL to fail to initialize. I did this, because some oversights were made in Vulkan 1.0. There are struct types which don't contain their type and a pointer to a next structure. But that information is required for SDL to iterate over the list.

@slouken slouken requested a review from thatcosmonaut October 12, 2025 16:29
@rabbit-ecl
Copy link
Contributor

It's worth noting that VkPhysicalDeviceVulkan11Features was actually added in Vulkan 1.2 (!) and the only way to enable Vulkan 1.1 features with a Vulkan 1.1 instance is to chain feature-specific structs like VkPhysicalDeviceShaderDrawParameterFeatures.

@TheSniperFan
Copy link
Author

TheSniperFan commented Oct 12, 2025

It's worth noting that VkPhysicalDeviceVulkan11Features was actually added in Vulkan 1.2 (!) and the only way to enable Vulkan 1.1 features with a Vulkan 1.1 instance is to chain feature-specific structs like VkPhysicalDeviceShaderDrawParameterFeatures.

In that case, I think it would be better to remove the whitelist and allow passing arbitrary struct chains into device creation. As @slouken said, when you're using properties, you're responsible for using them correctly.

@slouken
Copy link
Collaborator

slouken commented Oct 12, 2025

It's worth noting that VkPhysicalDeviceVulkan11Features was actually added in Vulkan 1.2 (!) and the only way to enable Vulkan 1.1 features with a Vulkan 1.1 instance is to chain feature-specific structs like VkPhysicalDeviceShaderDrawParameterFeatures.

In that case, I think it would be better to remove the whitelist and allow passing arbitrary struct chains into device creation. As @slouken said, when you're using properties, you're responsible for using them correctly.

Agreed.

@TheSniperFan
Copy link
Author

Looking a bit deeper into the specs, it looks like Vulkan 1.1 is an annoying special case. At that point, they still haven't quite figured out how they want to handle future version extensions yet. My current implementation works with 1.2+.

Saying that using props means you're responsible for using them correctly is fine, but I'd still like SDL to do some sanity checking. At least enough to where it can fail gracefully and report useful error messages rather than just crashing.

@TheSniperFan
Copy link
Author

Next draft. (Still WIP.)

/**
 * A structure specifying additional options when using Vulkan.
 *
 * When no such structure is provided, SDL will use Vulkan API version 1.0 and a minimal set of features.
 * The feature list gets passed to the vkCreateInstance function and allows requesting additional
 * features.
 * 
 * \since This struct is available since SDL 3.4.0.
 *
 */
typedef struct SDL_GPUVulkanOptions
{
    Uint32 vulkan_api_version; /**< The Vulkan API version to request for the instance. Use Vulkan's VK_MAKE_VERSION or VK_MAKE_API_VERSION. */
    void *feature_list; /**< Pointer to the first element of a list of structs to be passed to device creation. */
	void *vulkan_10_physical_device_features; /**< Pointer to a VkPhysicalDeviceFeatures struct to enable additional Vulkan 1.0 features. */
} SDL_GPUVulkanOptions;
  • Vulkan 1.0 features need to be passed separately, because the struct can't be chained. (This is one of the oversights I alluded to earlier.)
  • I implemented 3 code paths based on the api version that is passed.
  • Vulkan 1.0 only checks the provided 1.0 device features pointer.
  • Vulkan 1.1 additionally iterates over the list, collecting only the Vulkan 1.1 feature options. Thanks for bringing this to my attention, @rabbit-ecl
  • Vulkan 1.2+ additionally checks the VkPhysicalDeviceVulkanXXFeatures structs

E.g. Vulkan 1.3 features will be ignored if API version 1.2 or lower
is requested.
@TheSniperFan TheSniperFan changed the title Add hint to specify Vulkan API version to use Allow opting into Vulkan features Oct 12, 2025
@TheSniperFan TheSniperFan changed the title Allow opting into Vulkan features Allow SDL GPU to opt into additional Vulkan features Oct 12, 2025
@flibitijibibo
Copy link
Collaborator

This may have been answered already so apologies in advance, but is the slang compiler capable of using SPV extensions to meet 1.0 compatibility? It might be easier for us to make a property to add a list of VK extensions that allow newer features without needing to bump the whole runtime version.

@TheSniperFan
Copy link
Author

This may have been answered already so apologies in advance, but is the slang compiler capable of using SPV extensions to meet 1.0 compatibility? It might be easier for us to make a property to add a list of VK extensions that allow newer features without needing to bump the whole runtime version.

I am working on something like that. This pull request started off as a version selector, but morphed into something more flexible. If you're interested in specific features, please post them here. I'll see if they can be made to fit. @flibitijibibo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants